package edu.syr.iis.web.model;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.List;
import java.util.HashSet;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Transient;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Message
{
	private static Log log = LogFactory.getLog(Message.class);

	// For speaker
	public static final int Librarian = 1;
	public static final int User = 2;

	// For type
	public static final int Message = 1;
	public static final int Automatic = 2;
	public static final int Special = 3;

	int messageId;    

	int sequence;
	int speaker;
	int type;

	String dateTime;
	String content;
	String note;

	Conversation conversation;

	Set<DialogueActLabel> dialogueActLabels = new HashSet<DialogueActLabel>();	
	Set<DialogueAct> dialogueActs = new HashSet<DialogueAct>();

	DialogueAct[] dialogueActArray = null;
	SortedSet<DialogueAct> sortedDialogueActs = null;

	@Transient
	public int getNumberOfWords()
	{
		return content.split("\\s+").length;
	}

	/*
	 *  Returns the average length of the segments in the messages. 
	 */
	@Transient    
	public int getK(User u)
	{
		Collection<DialogueAct> das = getDialogueActs(u);
		int totalLength = 0;

		for (DialogueAct da: das)
			totalLength += (da.getEndIndex() - da.getStartIndex());

		return totalLength / das.size() / 2;
	}

	/* 
	 * Old version. I am not sure if it is a good idea. 
	 */
	@Transient
	public int getK0(User u)
	{
		int n = getNumberOfWords();
		int b = getNumberOfBoundaries(u, 0, n - 1);

		return (b < 2? n : n / (b - 1));  // two boundaries = one text segment  
	}



	@Transient 
	public double getWindowDiff (User u1, User u2)
	{
		int n = getNumberOfWords();
		int total = 0;

		// k is the average length of the segments in the message
		//
		int k1 = getK(u1);
		int k2 = getK(u2);
		int k = (k1 + k2) / 2;

		for (int i = 0; i < n - k; i++)
		{
			int b1 = getNumberOfBoundaries(u1, i, i + k);
			int b2 = getNumberOfBoundaries(u2, i, i + k);

			if (b1 != b2)
				total++;			
		}
		return (1.0 / (n - k)) * (double) total;		
	}

	@Transient
	public int getNumberOfBoundaries(User user, int start, int end)
	{		
		boolean [] boundaries = new boolean[getNumberOfWords()];

		for (DialogueAct da: dialogueActs)
		{
			if (da.getUser().userId == user.userId)
			{ 
				if (da.getStartIndex() != 0 && da.getStartIndex() != boundaries.length - 1) 
					boundaries[da.getStartIndex()] = true;
				if (da.getEndIndex() != 0 && da.getEndIndex() != boundaries.length -1) 
					boundaries[da.getEndIndex()] = true;
			}    			    		
		}

		int count = 0;
		for (int i = start; i <= end; i++)
			if (boundaries[i]) 
				count++;

		return count;
	}

	@Transient
	public int getNumberOfTextSegmentsBy(User user)
	{
		Set<Integer> boundaries = new HashSet<Integer>();

		for (DialogueAct da: dialogueActs)
		{
			if (da.getUser().userId == user.userId)
			{
				boundaries.add(new Integer(da.getStartIndex()));
				boundaries.add(new Integer(da.getEndIndex()));
			}    			    		
		}
		return boundaries.size() - 1;    	
	}




	public String getAgreementString()
	{
		Set<User> annotators = conversation.getAnnotators();
		String agreement = String.format("Overall: %.2f, ", getAgreement());

		for (User annotator: annotators)
			agreement += String.format("%s: %.2f, ", annotator.getUserName(), getAgreement(annotator));

		agreement = agreement.replaceFirst(",\\s$", "");
		return agreement;
	}

	public double getFunctionAgreement(User user)
	{
		Set <DialogueAct> dialogueActs = getDialogueActs(user);
		int count = 0;

		for (DialogueAct dialogueAct: dialogueActs)
		{
			// Assuming the non-XOR version of getMatchedDialogueActs()
			//
			if (dialogueAct.getMatchedDialogueActs(DialogueAct.MATCH_FUNCTION).size() > 0)
				count++;
		}
		// If the annotator hasn't annotated, it will return NaN
		//
		return ((double) count) / dialogueActs.size();
	}


	public double getMessageWiseAgreement(User user)
	{
		Set <DialogueAct> dialogueActs = getDialogueActs(user);
		int count = 0;

		for (DialogueAct dialogueAct: dialogueActs)
		{
			/*
			 * THis is an old version

			// these methods are mutually, exclusive so you need to call both of them 
			//
			if (dialogueAct.getMatchedDialogueActs().size() +			
				dialogueAct.getExactlyMatchedDialogueActs().size() > 0)
				count++;
			 */
			if (dialogueAct.getMatchedDialogueActs(DialogueAct.MATCH_DA).size() > 0)
				count++;				
		}
		// If the annotator hasn't annotated, it will return NaN
		//
		return ((double) count) / dialogueActs.size();
	}


	public double getAgreement(User user)
	{
		Set <DialogueAct> dialogueActs = getDialogueActs(user);
		int count = 0;

		for (DialogueAct dialogueAct: dialogueActs)
		{
			if (dialogueAct.getExactlyMatchedDialogueActs().size() > 0)
				count++;
		}
		// If the annotator hasn't annotated, it will return NaN
		//
		return ((double) count) / dialogueActs.size();
	}

	public double getAgreement()
	{
		Set<User> annotators = conversation.getAnnotators();
		double sum = 0.0;
		int count = 0;  // not using annotators.size(), because there are annotators that haven't done the job. 

		for (User annotator: annotators)
		{
			if (getDialogueActs(annotator).size() > 0)
			{
				sum += getAgreement(annotator);
				count++;
			}
		}

		return sum / count;
	}


	public Set<DialogueAct> getDialogueActs(User user)
	{
		Set<DialogueAct> das = new HashSet<DialogueAct>();

		for (DialogueAct da: dialogueActs)
			if (da.getUser().getUserId() == user.getUserId())
				das.add(da);
		return das;		
	}

	// Get the next dialogueAct annotated by the same annotator
	//
	public Collection<DialogueAct> getNextDialogueActs (DialogueAct da, User user, int speaker)
	{
		if (sequence == conversation.numMessages)
			new ArrayList<DialogueAct>();

		Message nextMessage = null;
		int i = 1; 
		while (nextMessage == null && i + sequence < conversation.numMessages)
		{
			Message m  = conversation.getMessageBySequence(sequence + i);
			if (m != null && m.speaker == speaker)
				nextMessage = m; 
			i++;
		}

		if (nextMessage == null) 
			return new ArrayList<DialogueAct>();
		else
			return nextMessage.getDialogueActs();
	}

	// Get the next dialogueAct annotated by the same annotator
	//
	public Collection<DialogueAct> getPreviousDialogueActs (DialogueAct da, User user, int speaker)
	{
		if (sequence == 1)
			return new ArrayList<DialogueAct>();

		Message nextMessage = null;
		int i = 1; 
		while (nextMessage == null && sequence - i > 0)
		{
			Message m  = conversation.getMessageBySequence(sequence - i);
			if (m != null && m.speaker == speaker)
				nextMessage = m; 
			i++;
		}

		if (nextMessage == null) 
			return new ArrayList<DialogueAct>();
		else
			return nextMessage.getDialogueActs();
	}

	// Get the next dialogueAct annotated by the same annotator
	//
	public DialogueAct getNextDialogueAct (DialogueAct da, User user)
	{
		DialogueAct [] daList = new DialogueAct[dialogueActs.size()];
		SortedSet<DialogueAct> sortedDas = getSortedDialogueActs(user);
		daList = sortedDas.toArray(daList);

		for (int i = 0; i < sortedDas.size(); i++) 
		{
			if (daList[i].getDialogueActId() == da.getDialogueActId() 
					&& i < sortedDas.size() - 1 && daList[i + 1] != null)
			{
				DialogueAct candidate =  (DialogueAct) daList[i + 1];
				//
				// If the next dialogue act is in the same text segment, ignore and move to the next one
				//
				if (candidate.getStartIndex() == da.getStartIndex())
					getNextDialogueAct(candidate, user);   
				else 
					return candidate;
			}
			// else	
			//	return null;				
		}
		if (sequence == conversation.numMessages)
			return null;

		Message nextMessage = conversation.getMessageBySequence(sequence + 1);

		if (nextMessage != null) 
			return nextMessage.getLastDialogueAct(user);

		return null;		
	}


	public DialogueAct getNextDialogueAct (DialogueAct da)
	{
		DialogueAct [] daList = new DialogueAct[dialogueActs.size()];	
		daList = getSortedDialogueActs().toArray(daList);

		for (int i = 0; i < daList.length; i++) 
		{
			if (daList[i] == da && i != daList.length - 1) 
			{
				DialogueAct candidate =  (DialogueAct) daList[i + 1];
				//
				// If the next dialogue act is in the same text segment, ignore and move to the next one
				//
				if (candidate.getStartIndex() == da.getStartIndex())
					getNextDialogueAct(candidate);   
				else 
					return candidate;
			}
		}

		if (sequence == conversation.numMessages)
			return null;

		Message nextMessage = conversation.getMessageBySequence(sequence + 1);
		if (nextMessage != null) 
			return nextMessage.getLastDialogueAct();
		else	
			return null;		
	}

	// Get the previous dialogueAct annotated by the same annotator
	// and from the specified speaker    
	//
	public DialogueAct getPreviousDialogueAct (DialogueAct da, User user, int speaker)
	{
		DialogueAct [] daList = new DialogueAct[dialogueActs.size()];
		SortedSet<DialogueAct> sortedDas = getSortedDialogueActs(user);
		daList = (DialogueAct []) sortedDas.toArray(daList);

		for (int i = 0; i < sortedDas.size(); i++) 
		{			
			if (daList[i].getDialogueActId() == da.getDialogueActId() 
					&& i != 0 && daList[i - 1] != null)
			{
				DialogueAct candidate =  (DialogueAct) daList[i - 1];
				//
				// If the next dialogue act is in the same text segment, ignore and move to the next one
				//
				if (candidate.getStartIndex() == da.getStartIndex())
					getPreviousDialogueAct(candidate, user);   
				else 
					return candidate;			
			}
		}

		if (sequence == 1)
			return null;

		Message prevMessage = conversation.getMessageBySequence(sequence - 1);
		if (prevMessage != null) 
			return prevMessage.getLastDialogueAct(user);

		return null;		
	}


	// Get the previous dialogueAct annotated by the same annotator
	//
	public DialogueAct getPreviousDialogueAct (DialogueAct da, User user)
	{
		DialogueAct [] daList = new DialogueAct[dialogueActs.size()];
		SortedSet<DialogueAct> sortedDas = getSortedDialogueActs(user);
		daList = (DialogueAct []) sortedDas.toArray(daList);

		for (int i = 0; i < sortedDas.size(); i++) 
		{			
			if (daList[i].getDialogueActId() == da.getDialogueActId() 
					&& i != 0 && daList[i - 1] != null)
			{
				DialogueAct candidate =  (DialogueAct) daList[i - 1];
				//
				// If the next dialogue act is in the same text segment, ignore and move to the next one
				//
				if (candidate.getStartIndex() == da.getStartIndex())
					getPreviousDialogueAct(candidate, user);   
				else 
					return candidate;			
			}
		}

		if (sequence == 1)
			return null;

		Message prevMessage = conversation.getMessageBySequence(sequence - 1);
		if (prevMessage != null) 
			return prevMessage.getLastDialogueAct(user);

		return null;		
	}


	public DialogueAct getPreviousDialogueAct (DialogueAct da)
	{
		DialogueAct [] daList = new DialogueAct[dialogueActs.size()];	
		daList =  this.getSortedDialogueActs().toArray(daList);

		for (int i = 0; i < daList.length; i++) 
		{			
			if (daList[i].getDialogueActId() == da.getDialogueActId() && i != 0) 
			{
				DialogueAct candidate =  (DialogueAct) daList[i - 1];
				//
				// If the next dialogue act is in the same text segment, ignore and move to the next one
				//
				if (candidate.getStartIndex() == da.getStartIndex())
					getPreviousDialogueAct(candidate);   
				else 
					return candidate;			
			}
		}

		if (sequence == 1)
			return null;

		Message prevMessage = conversation.getMessageBySequence(sequence - 1);
		if (prevMessage != null) 
			return prevMessage.getLastDialogueAct();
		return null;		
	}

	public DialogueAct getLastDialogueAct()
	{
		SortedSet<DialogueAct> das = getSortedDialogueActs();
		if (!das.isEmpty())
			return das.last();
		else
			return null;		
	}

	public DialogueAct getLastDialogueAct(User user)
	{
		SortedSet<DialogueAct> das = getSortedDialogueActs(user);
		if (!das.isEmpty())
			return das.last();
		else
			return null;		
	}


	public DialogueAct getFirstDialogueAct(User user)
	{
		SortedSet<DialogueAct> das = getSortedDialogueActs(user);
		if (!das.isEmpty())
			return das.first();
		else
			return null;
	}


	public DialogueAct getFirstDialogueAct()
	{
		SortedSet<DialogueAct> das = getSortedDialogueActs();
		if (!das.isEmpty())
			return das.first();
		else
			return null;
	}

	public DialogueAct[] getDialogueActArray()
	{
		if (dialogueActArray == null || dialogueActArray.length != dialogueActs.size()) {     		
			dialogueActArray = dialogueActs.toArray(new DialogueAct[0]);
		}

		return dialogueActArray; 
	}        

	public SortedSet<DialogueAct> getSortedDialogueActs(User u)
	{    	
		// log.debug("Entering getSortedDialogueActs() with dialogueActs of size: " + dialogueActs.size());

		SortedSet<DialogueAct> sortedDas = new TreeSet<DialogueAct>(new DialogueActComparator());

		for (DialogueAct dialogueAct: dialogueActs)
			if (dialogueAct.getUser().getUserId() == u.getUserId())
				sortedDas.add(dialogueAct);

		return sortedDas;
	}


	public SortedSet<DialogueAct> getSortedDialogueActs()
	{    	
		log.debug("Entering getSortedDialogueActs() with dialogueActs of size: " + dialogueActs.size());

		// if (sortedDialogueActs == null || sortedDialogueActs.size() != dialogueActs.size()) 
		// {    		
		SortedSet<DialogueAct> sortedDas = new TreeSet<DialogueAct>(new DialogueActComparator());
		for (DialogueAct dialogueAct: dialogueActs)
			sortedDas.add(dialogueAct);
		sortedDialogueActs = sortedDas;
		// }
		log.debug("Existing getSortedDialogueActs() with sortedDialogueActs of size: " + sortedDialogueActs.size());
		return sortedDialogueActs;
	}



	public String getNote()
	{
		if (note == null || note.matches("^ *$")) {
			return "";
		}

		return note;
	}

	public void setNote(String note)
	{
		if (note != null)
			this.note = note.trim();
		else
			this.note = null;
	}


	public String getContentStringEscaped()
	{
		return content.trim().replaceAll("\"", "&quot;");
	}

	public String getContentStringPlain()
	{
		//System.out.println("Content: " + content + ";");

		if (content.startsWith("http:"))
		{
			return (content.length() > 60)? content.substring(0, 59) + "..." : content;
		} 
		else if (content.matches("^\\[.*\\]\\s*$"))
		{
			return new String(content.replace('_', ' ').trim());
		}
		return content.trim();
	}

	public String getContentString()
	{
		String shortContent;

		//System.out.println("Content: " + content + ";");

		if (content.startsWith("http:"))
		{
			shortContent = (content.length() > 60)? content.substring(0, 59):content;
			return new String("<a href=\"" + content + "\">" + shortContent  + "..." + "</a>");	    
		} 
		else if (content.matches("^\\[.*\\]\\s*$"))
		{
			return new String(content.replace('_', ' ').trim());
		}
		return content.trim();
	}


	public List<WordListItem> getWordList()
	{

		String contentCopy = new String(content);

		if (contentCopy.matches("^\\[.*\\]\\s*$"))
			contentCopy = contentCopy.replace('_', ' ');
		String[] words = contentCopy.split("(\\s+)");
		List<WordListItem> wordList = new ArrayList<WordListItem>();

		int index = 0;

		for (String word: words)
		{
			wordList.add(index, new WordListItem(word, index));
			index++;
		}
		return wordList;
	}

	public class WordListItem
	{
		String word;
		int index;

		public WordListItem(String word, int index) {
			super();
			this.word = word;
			this.index = index;
		}

		public String getWord() {
			return word;
		}
		public void setWord(String word) {
			this.word = word;
		}
		public int getIndex() {
			return index;
		}
		public void setIndex(int index) {
			this.index = index;
		}			
	}

	public Conversation getConversation()
	{
		return conversation;
	}
	public void setConversation(Conversation conversation)
	{
		this.conversation = conversation;
	}
	public int getMessageId()
	{
		return messageId;
	}
	public int getSequence()
	{
		return sequence;
	}
	public int getSpeaker()
	{
		return speaker;
	}

	public String getTime()
	{	
		return dateTime.substring(dateTime.indexOf(" "));
	}

	public String getDateTime()
	{
		return dateTime;
	}
	public String getContent()
	{
		return content;
	}
	public int getType()
	{
		return type;
	}
	public void setMessageId(int messageId)
	{
		this.messageId = messageId;
	}
	public void setSequence(int sequence)
	{
		this.sequence = sequence;
	}
	public void setSpeaker(int speaker)
	{
		this.speaker = speaker;
	}
	public void setDateTime(String dateTime)
	{
		this.dateTime = dateTime;
	}
	public void setContent(String content)
	{
		this.content = content;
	}
	public void setType(int type)
	{
		this.type = type;
	}

	public Set<DialogueActLabel> getDialogueActLabels()
	{
		return dialogueActLabels;
	}

	@OneToMany(mappedBy="message", targetEntity=DialogueAct.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL)
	public Set<DialogueAct> getDialogueActs()
	{		
		return dialogueActs;
	}

	public void setDialogueActLabels(Set<DialogueActLabel> dialogueActLabels)
	{
		this.dialogueActLabels = dialogueActLabels;
	}


	public void setDialogueActs(Set<DialogueAct> dialogueActs)
	{
		this.dialogueActs = dialogueActs;
	}

	// Does not work, because the new DA is not defined.
	//
	public void addDialogueAct()
	{    	
		this.dialogueActs.add(new DialogueAct());
	}

	public void addDialogueAct(DialogueAct da)
	{	
		dialogueActs.add(da);
	}

	public void deleteDialogueAct(int daId)
	{	
		HashSet<DialogueAct> newDAs = new HashSet<DialogueAct>();
		System.out.println("Size of dialogueActs: " + dialogueActs.size());
		System.out.println("Removing from Set: " + daId);

		for (DialogueAct curDA: this.dialogueActs)
		{
			if (curDA.getDialogueActId() == daId)
				System.out.println("Found the DA ID: " + curDA.getDialogueActId());
			else
				newDAs.add(curDA);
		}	
		this.dialogueActs = newDAs;
		System.out.println("Size of dialogueActs: " + dialogueActs.size());
	}

}
